#!/usr/bin/python

from hashdb_output import log
from hashdb_config_base import *
from hashdb_config_defaults import *
import re

def parse_config_files(filenames=None, settings=None, override=False):
    if settings != None:
        filenames = settings.configs if (filenames == None) else filenames + settings.configs

    if filenames == CONFIG_CONFIGS:
        todo = [(f,True) for f in filenames]
    else:
        todo = [(f,False) for f in filenames]

    done = set()
    found = False
    subsettings = ConfigSettings()
    while len(todo) != 0:
        filename, is_default = todo.pop()

        try:
            with open(filename, 'rt') as fp:
                stat = os.fstat(fp.fileno())
                if (stat.st_dev, stat.st_ino) not in done:
                    done.add((stat.st_dev, stat.st_ino))
                    parse_config_file(fp, subsettings, False)
                    todo.extend([(f,False) for f in subsettings.configs])
        except OSError, ex:
            if not is_default:
                log.warning('warning: unable to open/process config file (%s): %s' % (filename, ex))
        except IOError, ex:
            if not is_default:
                log.warning('warning: unable to open/process config file (%s): %s' % (filename, ex))

    if settings == None:
        return subsettings

    settings.update(subsettings, override)
    return settings



def parse_config_file(fp, settings=None, override=True):
    re_namevalue = re.compile(
        r'''
        ^                 # begining of line
        \s*               # whitespace
        (?P<n>[a-zA-Z_]+) # name
        \s*               # whitespace
        =                 # =
        \s*               # whitespace
        (?P<v>.*?)        # value
        \s*               # whitespace
        $                 # end of string
        ''',
        re.VERBOSE
    )
    re_skip = re.compile(
        r'''
        ^
        (?:
            \s*  |
            (?: ; | \# | // ) .*
        )
        $
        ''',
        re.VERBOSE
    )

    # name: (target, parser, default (for first argument of fcombine), fcombine)
    parse_mappings = {
        'verbosity'       : ('verbosity'        , parse_text__verbosity,        None, lambda x,y: y),
        'config'          : ('configs'          , parse_text__filename,           [], lambda x,y: x + [y]),
        'use_updatedb'    : ('updatedb'         , parse_text__boolean,          None, lambda x,y: CONFIG_UPDATEDB),
        'updatedb'        : ('updatedb'         , parse_text__filename,         None, lambda x,y: y),
        'walk_depth'      : ('walk_depth'       , parse_text__boolean,          None, lambda x,y: y),
        'database'        : ('database'         , parse_text__filename,         None, lambda x,y: y),
        'databases_local' : ('databases_locals' , parse_text__filename,           [], lambda x,y: x + [y]),
        'databases_remote': ('databases_remotes', parse_text__filename,           [], lambda x,y: x + [y]),
        'databases_join'  : ('databases_joins'  , parse_text__filenames,          [], lambda x,y: x + [CombineDB(*y)]), # Todo: make this a proper parse with decent error reporting

        'skip_mounts'     : ('skip_mounts'      , parse_text__boolean,          None, lambda x,y: y),
        'skip_binds'      : ('skip_binds'       , parse_text__boolean,          None, lambda x,y: y),
        'skip_symlinks'   : ('skip_symlinks'    , parse_text__boolean,          None, lambda x,y: y),

        'skip_fstype'     : ('skip_fstypes'     , parse_text__filename,           [], lambda x,y: x + [y]),
        'skip_path'       : ('skip_paths'       , parse_text__filename,           [], lambda x,y: x + [y]),
        'skip_name'       : ('skip_names'       , parse_text__filename,           [], lambda x,y: x + [y]),
        'skip_dirname'    : ('skip_dirnames'    , parse_text__filename,           [], lambda x,y: x + [y]),
        'skip_filename'   : ('skip_filenames'   , parse_text__filename,           [], lambda x,y: x + [y]),

        'skip_fstypes'    : ('skip_fstypes'     , parse_text__filenames,          [], lambda x,y: x + y),
        'skip_paths'      : ('skip_paths'       , parse_text__filenames,          [], lambda x,y: x + y),
        'skip_names'      : ('skip_names'       , parse_text__filenames,          [], lambda x,y: x + y),
        'skip_dirnames'   : ('skip_dirnames'    , parse_text__filenames,          [], lambda x,y: x + y),
        'skip_filenames'  : ('skip_filenames'   , parse_text__filenames,          [], lambda x,y: x + y),

##        'hash_definitive' : ('hash_definitive'  , parse_text__boolean,          None, lambda x,y: y),
##        'hash_force'      : ('hash_force'       , parse_text__boolean,          None, lambda x,y: y),
##        'hash_target'     : ('hash_targets'     , parse_text__filename,           [], lambda x,y: x + [y]),
##        'hash_targets'    : ('hash_targets'     , parse_text__filenames,          [], lambda x,y: x + y),
##
##        'match_verify'    : ('match_verify'     , parse_text__boolean,          None, lambda x,y: y),
##        'match_target'    : ('match_targets'    , parse_text__filename,           [], lambda x,y: x + [y]),
##        'match_targets'   : ('match_targets'    , parse_text__filenames,          [], lambda x,y: x + y),
##
##        'view_local'      : ('view_locals'      , parse_text__filename,           [], lambda x,y: x + [y]),
##        'view_locals'     : ('view_locals'      , parse_text__filenames,          [], lambda x,y: x + y),
##        'view_remote'     : ('view_remotes'     , parse_text__filename,           [], lambda x,y: x + [y]),
##        'view_remotes'    : ('view_remotes'     , parse_text__filenames,          [], lambda x,y: x + y),
##        'view_database'   : ('view_databases'   , parse_text__filename,           [], lambda x,y: x + [y]),
##        'view_databases'  : ('view_databases'   , parse_text__filenames,          [], lambda x,y: x + y),
##
##        'query_sql'       : ('query_sql'        , parse_text__verbatim,         None, lambda x,y: y),
##
##        'cmd'             : ('cmd'              , parse_text__choice(['hash','match','view','query','cmd']), None, lambda x,y: y),
    }

    if settings == None:
        settings = ConfigSettings()

    for lineno, line in enumerate(fp):
        match = re_namevalue.match(line)
        if not match:
            match = re_skip.match(line)
            if not match:
                log.warning('warning: invalid line in config file (lineno: %d)' % lineno)
            continue

        name  = match.group('n').lower()
        value = match.group('v')

        if name not in parse_mappings:
            log.warning('warning: unknown setting (%s) in config file (%s)' % (name, filename))
        else:
            target, fparse, default, fcombine = parse_mappings[name]

            try:
                value = fparse(value)
            except Exception, ex:
                log.warning('warning: invalid setting (%s) for (%s) in config file (%s)' % (value, name, filename))

            if override or settings.is_default(target):
                settings[target] = fcombine(settings.get(target, default) if ((not override) or settings.is_default(target)) else default, value)

    return settings

if __name__ == '__main__':
    with open('test.conf', 'wt') as fp:
        fp.write("""
verbosity         = debug
config            = test-base.conf
use_updatedb      = True
updatedb          = "/etc/updatedb.conf"
walk_depth        = True
database          = /database.db
databases_local   = /local.db
databases_remote  = /remote.db
databases_join    = /local/path join.db /remote/path

skip_mounts       = True
skip_binds        = False
skip_symlinks     = Y

skip_path         = p*th
skip_name         = n*me
skip_dirname      = dir*name
skip_filename     = file*name

skip_paths        = a*b x*y "qu*ted"
skip_names        = a*b x*y "qu*ted"
skip_dirnames     = a*b x*y "qu*ted"
skip_filenames    = a*b x*y "qu*ted"
        """)

    with open('test-base.conf', 'wt') as fp:
        fp.write("""
verbosity         = debug
config            = does-not-exist.conf
use_updatedb      = False
database          = /root.db

skip_fstype       = root-fs*type
skip_path         = root-p*th
skip_name         = root-n*me
skip_dirname      = root-dir*name
skip_filename     = root-file*name
        """)

    print parse_config_files(['test.conf'])